package com.neo.tiny.demo.sso.api;

import cn.hutool.core.util.StrUtil;
import com.neo.tiny.demo.sso.common.ResResult;
import com.neo.tiny.demo.sso.dto.OAuth2AccessTokenRespDTO;
import com.neo.tiny.demo.sso.dto.OAuth2CheckTokenRespDTO;
import com.neo.tiny.demo.sso.dto.UserInfoRespDTO;
import com.neo.tiny.demo.sso.config.security.OauthProperties;
import com.neo.tiny.demo.sso.config.security.model.AdminUserDetails;
import com.neo.tiny.demo.sso.util.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description: 与用户中心数据交换
 * @Author: yqz
 * @CreateDate: 2022/11/13 20:22
 */
@Component
public class OauthApi {

    @Autowired
    private OauthProperties oauthProperties;

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 根据code 换取token
     *
     * @param code 授权code
     * @return token
     */
    public ResResult<OAuth2AccessTokenRespDTO> postAccessToken(String code) {
        // 1.1 构建请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        addClientHeader(headers);
        // 1.2 构建请求参数
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "authorization_code");
        body.add("code", code);
        body.add("redirect_uri", oauthProperties.getRedirectUri());
//        body.add("state", ""); // 选填；填了会校验

        // 2. 执行请求
        ResponseEntity<ResResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange(
                oauthProperties.getBaseUrl() + "/open/oauth2/token",
                HttpMethod.POST,
                new HttpEntity<>(body, headers),
                new ParameterizedTypeReference<ResResult<OAuth2AccessTokenRespDTO>>() {
                }); // 解决 CommonResult 的泛型丢失
        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
        return exchange.getBody();
    }

    /**
     * 校验token
     *
     * @param accessToken token
     * @return 访问令牌的基本信息
     */
    public ResResult<OAuth2CheckTokenRespDTO> checkAccessToken(String accessToken) {
        // 1.1 构建请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        addClientHeader(headers);
        // 1.2 构建请求参数
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("token", accessToken);

        // 2. 执行请求
        ResponseEntity<ResResult<OAuth2CheckTokenRespDTO>> exchange = restTemplate.exchange(
                oauthProperties.getBaseUrl() + "/open/oauth2/check-token",
                HttpMethod.POST,
                new HttpEntity<>(body, headers),
                new ParameterizedTypeReference<ResResult<OAuth2CheckTokenRespDTO>>() {
                });
        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
        return exchange.getBody();
    }

    /**
     * 使用刷新令牌，获得（刷新）访问令牌
     *
     * @param refreshToken 刷新令牌
     * @return 访问令牌
     */
    public ResResult<OAuth2AccessTokenRespDTO> refreshToken(String refreshToken) {
        // 1.1 构建请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        addClientHeader(headers);
        // 1.2 构建请求参数
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "refresh_token");
        body.add("refresh_token", refreshToken);

        // 2. 执行请求
        ResponseEntity<ResResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange(
                oauthProperties.getBaseUrl() + "/open/oauth2/token",
                HttpMethod.POST,
                new HttpEntity<>(body, headers),
                new ParameterizedTypeReference<ResResult<OAuth2AccessTokenRespDTO>>() {
                });
        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
        return exchange.getBody();
    }

    /**
     * 删除访问令牌
     *
     * @param accessToken 访问令牌
     * @return 成功
     */
    public ResResult<Boolean> removeToken(String accessToken) {
        // 1.1 构建请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        addTokenHeader(headers);
        // 1.2 构建请求参数
        Map<String, Object> body = new HashMap<>();
        body.put("accessToken", accessToken);
        // 退出范围：0-本应用，1-全平台
        body.put("logoutScope", 0);

        // 2. 执行请求
        ResponseEntity<ResResult<Boolean>> exchange = restTemplate.exchange(
                oauthProperties.getBaseUrl() + "/open/oauth2/logout",
                HttpMethod.DELETE,
                new HttpEntity<>(body, headers),
                new ParameterizedTypeReference<ResResult<Boolean>>() {
                });
        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
        return exchange.getBody();
    }

    public ResResult<UserInfoRespDTO> getUserInfo() {
        // 1.1 构建请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        addTokenHeader(headers);
        // 1.2 构建请求参数
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();

        // 2. 执行请求
        ResponseEntity<ResResult<UserInfoRespDTO>> exchange = restTemplate.exchange(
                oauthProperties.getBaseUrl() + "/open/oauth2-user/get",
                HttpMethod.GET,
                new HttpEntity<>(body, headers),
                new ParameterizedTypeReference<ResResult<UserInfoRespDTO>>() {
                });
        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
        return exchange.getBody();
    }

    private static void addTokenHeader(HttpHeaders headers) {
        AdminUserDetails user = SecurityUtils.getUser();
        Assert.notNull(user, "登录用户不能为空");
        headers.add("Authorization", "Bearer " + user.getAccessToken());
    }

    private void addClientHeader(HttpHeaders headers) {
        // client 拼接，需要 BASE64 编码
        String clientId = oauthProperties.getClientId();
        String before = clientId + StrUtil.COLON + clientId;
        String basic = Base64Utils.encodeToString(before.getBytes(StandardCharsets.UTF_8));
        headers.add("Authorization", "Basic " + basic);
    }
}
